#  This is part of Scopia Visual Framework
#
#  Scene tree for building a scene classes and functions. They
#  handle automatic display generation when they are not being
#  animated (dynamicChilds mode).
#
#  Copyright (C) 2008  Scopia Visual Interfaces Systems 
#                      http://www.scopia.es
#
#  author: Alejandro Conty
#  contributors:
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.

from numpy import *
from OpenGL.GL import *
import sys

class RenderNode(object):
    """Generic render node, it has three possible states:
            static      dynamicChilds == dynamic == False, it is rendered using a display list
            dynamicChilds        dynamicChilds == True, dynamic == False, some child nodes are changing
                        and therefore draws using no display list
            dynamic  dynamicChilds == True, dynamic == True, it is changing itself and
                        therefore draws using no display list"""
    # display list
    Lp = None
    # modes
    dynamicChilds = False
    dynamic = False
    # parent node
    parent = None
    #whenever to render children before or after itself
    renderChildFirst = True

    def __init__(self, context, style):
        """Init function, takes the context and a style instance for
           building geometry and stuff"""
        self.context = context
        self.style = style
        self.begin = True

    def __del__(self): self.reset()

    def free(self): self.reset()
    def renderAll(self):
        """This function is implemented by child classes and must render
           everything without using display lists. It is guaranteed to be
           called at least once outside display list compilation so it can
           load any textures and stuff it might need."""
        pass

    def buildNodeList(self):
        "Compiles a display list for this node"
        if self.Lp: return
        self.Lp = glGenLists(1)
        glNewList( self.Lp, GL_COMPILE)
        self.renderAll()
        glEndList()

    
    def render(self):
        "This is the render function that the caller will invoke, shouldn't be overriden in child classes"
        # at least we render normally once
        if self.begin:
            self.renderAll()
            self.begin = False
            return
        # then if in some of the two dynamicChilds modes we render with no display list
        if self.dynamicChilds: self.renderAll()
        else:
            # otherwise use a display list, build if not ready
            if not self.Lp: self.buildNodeList()
            glCallList( self.Lp )

    def reset(self):
        "Destroys the display list"
        if self.Lp: glDeleteLists( self.Lp, 1 )
        self.Lp = None

    def update(self):
        "Update the drawing of this node and the above branche in the tree"
        self.reset()
        if self.parent: self.parent.update()

    def goDynamic(self):
        "Switch to dynamicChilds mode cause this node will be changing from now on"
        self.reset()
        self.dynamic = self.dynamicChilds = True
        # tell the parent it must go dynamicChilds cause something is going on here
        if self.parent: self.parent._goDynamic()

    def _goDynamic(self):
        "Switch to dynamicChilds mode cause some children are changing"
        self.reset()
        self.dynamicChilds = True
        # tell the parent it must go dynamicChilds cause something is going on in some child
        if self.parent: self.parent._goDynamic()
    
    def goStatic(self):
        """Switch to static mode because the node itself has stoped changing. If the
           node can have children, this should be overriden to check them before going
           static."""
        self.dynamic = False
        self.dynamicChilds = False
        # tell the parent that everything is quiet now here
        if self.parent: self.parent._goStatic()

    def _goStatic(self):
        """A child has stopped changing so try to switch to static. If this node can have 
           several children, it should override this function to check if all of them have
           stopped changing. """
        if not self.dynamic:
            self.dynamicChilds = False
            if self.parent: self.parent._goStatic()

    def getLocalCoords( self, winCoords, Z ):
        """Returns local coords based on a window position (pixel units).
           If it is normal node three elements are returned: 
                   (x,y,z): 3D position
                   (dx, dy, dz): delta vector in x direction (per pixel unit)
                   (dx, dy, dz): delta vector in y direction (per pixel unit)
           If it is a viewport width and height are returned."""
        if self.parent: return self.parent.getLocalCoords( winCoords, Z )
        else: return None
    
class RenderSingleChildNode(RenderNode):

    def __init__(self, context, style, child ):
        RenderNode.__init__(self, context, style)
        child.parent = self
        self.child = child

    def __del__(self):
        self.free()
        RenderNode.__del__(self)

    def renderAll(self):
        if self.child: self.child.render()

    def buildNodeList(self):
        if self.Lp: return
        if self.child: self.child.buildNodeList()
        self.Lp = glGenLists(1)
        glNewList( self.Lp, GL_COMPILE)
        self.renderAll()
        glEndList()
    
    def goStatic(self):
        self.dynamic = False
        if self.child.dynamicChilds: return
        self.dynamicChilds = False
        if self.parent: self.parent._goStatic()

    def _goStatic(self):
        if not self.dynamic:
            if self.child.dynamicChilds: return
            self.dynamicChilds = False
            if self.parent: self.parent._goStatic()
    
    def free(self):
        self.reset()
        if self.child: 
            self.child.parent = None
            self.child.free()
        self.child = None

class RenderComposeNode(RenderNode):
    "Node composed of several child nodes"

    def __init__(self, context, style, children = None):
        RenderNode.__init__(self, context, style)
        if children: self.children = children
        else: self.children = []
        for child in self.children:
            child.parent = self

    def __del__(self):
        for c in self.children: c.free()
        RenderNode.__del__(self)

    def renderAll(self):
        for c in self.children: c.render()

    def buildNodeList(self):
        if self.Lp: return
        # first make sure children have built all their display lists
        for c in self.children: c.buildNodeList()
        # then build the list with the whole branch
        self.Lp = glGenLists(1)
        glNewList( self.Lp, GL_COMPILE)
        self.renderAll()
        glEndList()
    
    def goStatic(self):
        # at least we disable itself dynamicChilds status
        self.dynamic = False
        # if a child is changing keep dynamicChilds mode
        for c in self.children: 
            if c.dynamicChilds: return
        # switch off dynamicChilds and tell parent
        self.dynamicChilds = False
        if self.parent: self.parent._goStatic()

    def _goStatic(self):
        if not self.dynamic:
            for c in self.children: 
                if c.dynamicChilds: return
            self.dynamicChilds = False
            if self.parent: self.parent._goStatic()
    
    def appendChild(self, child):
        "Add a render node as child of this compose render node"
        child.parent = self
        self.children.append( child )

    def removeChild(self, child):
        "Remove a child render node from here"
        child.free()
        del self.children[ self.children.index(child) ]
        
    
    def free(self):
        "Cascade free"
        RenderNode.free(self)
        for c in self.children: 
            del c.parent
            c.free()
        self.children = []

class RenderGeomNode(RenderNode):
    "Simple geometry render node, no children, only needs a function"
    def renderAll(self): pass

#class RenderTransformNode(RenderSingleChildNode):
#    "Transform node. It has only one child and a matrix for tranforming"
#
#    def __init__(self, context, style, child):
#        RenderSingleChildNode.__init__(self, context, style, child)
#        self.M = identity(4, float32)
#
#    def renderAll(self):
#        if self.child:
#            glPushMatrix()
#            glMultMatrixf(self.M)
#            self.child.render()
#            glPopMatrix()
#
#    def identity(self):
#        "Sets the identity (no transform)"
#        self.M = identity(4, float32)
#    def translate(self, V):
#        "Applies a translation to the current transform"
#        self.M = dot( self.M, array( ( (1,0,0,V[0]), (0,1,0,V[1]), (0,0,1,V[2]),(0,0,0,1) ), float32) )
#    def scale(self, V):
#        "Applies a scaling to the current transform"
#        self.M = dot( self.M, array( ( (V[0],0,0,0), (0,V[1],0,0), (0,0,V[2],0),(0,0,0,1) ), float32) )
#    def done(self):
#        "Transform ready to be applied"
#        self.M = transpose(self.M).copy()
#
class RenderSimpleTransformNode(RenderSingleChildNode):
    "A faster and less generic (only scale and translation) transform node with a single child"

    def __init__(self, context, style, child):
        RenderSingleChildNode.__init__(self, context, style, child)
        self.translate = (0.0, 0.0, 0.0)
        self.scale = (1.0, 1.0, 1.0)
        self.scaleFirst = False

    def renderAll(self):
        if self.child:
            glPushMatrix()
            if self.scaleFirst:
                if self.scale != None: glScalef(* self.scale )
                if self.translate != None: glTranslatef(* self.translate )
            else:
                if self.translate != None: glTranslatef(* self.translate )
                if self.scale != None: glScalef(* self.scale )
            self.child.render()
            glPopMatrix()

    def set(self, trans = None, scale = None ):
        "Sets the translation and scaling vectors"
        self.translate = trans
        self.scale = scale

    def getLocalCoords( self, winCoords, Z ):
        if not self.parent: return None
        try: position, deltaX, deltaY = self.parent.getLocalCoords( winCoords, Z )
        except TypeError, ex: return None
        position -= self.translate
        position /= self.scale
        deltaX /= self.scale
        deltaY /= self.scale
        return (position, deltaX, deltaY)

class RenderOrthoNode(RenderSingleChildNode):
    "Ortho view node. It will render its only child with an ortho projection"

    def __init__(self, context, style, child, viewCenter, viewRadiuses ):
        RenderSingleChildNode.__init__(self, context, style, child)
        self.viewCenter = viewCenter
        self.viewRadiuses = viewRadiuses
        self.goDynamic()

    def renderAll(self):
        if self.child:
            aspectRatio = float(self.context.width) / float(self.context.height)
            self.context.putOrtho( self.viewCenter[0] - self.viewRadiuses[0]*aspectRatio,
                                   self.viewCenter[0] + self.viewRadiuses[0]*aspectRatio,
                                   self.viewCenter[1] - self.viewRadiuses[1],
                                   self.viewCenter[1] + self.viewRadiuses[1] )
            self.child.render()
            self.context.restoreView()
    
    def getLocalCoords( self, winCoords, Z ):
        if not self.parent: return None
        # there must be a viewport over this mode, never a transform or projection
        try: width, height = self.parent.getLocalCoords( winCoords, Z )
        except TypeError, ex: return None
        except ValueError, ex: raise Exception('Bad scene node composition detected')
        aspectRatio = float(width) / float(height)
        pxtox = self.viewRadiuses[0]*2.0*aspectRatio/float(width)
        pytoy = self.viewRadiuses[1]*2.0/float(height)
        position = array( ( pxtox*winCoords[0], pytoy*winCoords[1], Z ), float32 )
        deltaX = array( ( pxtox, 0, 0 ), float32 )
        deltaY = array( ( 0, pytoy, 0 ), float32 )
        return (position, deltaX, deltaY)

class RenderCameraNode(RenderSingleChildNode):
    "Camera view node. It will render its only child with a camera projection"
    viewAngle = 45.0

    def __init__(self, context, style, child, viewFrom = (1,0,0), viewTo=(0,0,0), viewUp=(0,0,1) ):
        RenderSingleChildNode.__init__(self, context, style, child)
        self.viewFrom = viewFrom
        self.viewTo = viewTo
        self.viewUp = viewUp
        self.clipStart = 0.1
        self.clipEnd = 100.0
        self.goDynamic()

    def renderAll(self):
        if self.child:
            aspectRatio = float(self.context.width) / float(self.context.height)
            self.context.putView( self.viewFrom, self.viewTo, self.viewUp, 
                self.viewAngle, self.clipStart, self.clipEnd, self.clipEnd*0.3)
            self.child.render()
            self.context.restoreView()
    
    def getLocalCoords( self, winCoords, Z ): pass
        # TODO

class RenderRotCameraNode( RenderCameraNode ):
    "Rotating camera class"

    def __init__(self, context, style, child, center, radius, angle):
        """Creates a camera rotating around a 'center' at a distance of
           'radius' and with a height angle of 'angle'."""
        RenderCameraNode.__init__(self, context, style, child)
        self.viewTo = array(center, float32)
        self.radius = array(radius, float32)
        self.hangle = angle
        self.angle = 0.0
        self.angleMove = 0.5
        self.rotating = False
        self.goDynamic()
        self.update()

    def update( self ):
        a = self.angle
        V = array( ( math.cos(a), math.sin(a), math.sin(self.hangle) ), float32 )
        self.viewFrom = self.viewTo + V*self.radius
        self.clipStart = 0.1*self.radius
        self.clipEnd = 12.0*self.radius

    def timer(self, elapsed):
        self.angle += elapsed * self.angleMove
        self.update()
        return self.rotating

    def startRotation(self, speed = 0.5):
        self.angleMove = speed
        self.rotating = True
        self.context.addTimer(self.timer)

    def stopRotation(self):
        self.rotating = False

class RenderViewportNode(RenderSingleChildNode):

    def __init__(self, context, style, child, x, y, w, h ):
        RenderSingleChildNode.__init__(self, context, style, child)
        self.upleft = ( x, y )
        self.goDynamic() # and never put this into static
        self.width = w 
        self.height = h 

    def renderAll(self):
        if self.child:
            self.context.setViewport( int(self.upleft[0]*self.context.width), 
                                      int(self.upleft[1]*self.context.height), 
                                      int(self.width*self.context.width), 
                                      int(self.height*self.context.height) )
            self.child.render()
    
    def getLocalCoords( self, winCoords, Z ):
        return (int(self.width*self.context.width), int(self.height*self.context.height) )
